/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.auth;

import com.je.auth.config.OAuth2Config;
import com.je.auth.check.context.model.AuthRequest;
import com.je.auth.check.context.model.AuthResponse;
import com.je.auth.exception.OAuth2Exception;
import com.je.auth.model.*;
import com.je.auth.resource.ResourceLoaderRegistry;
import com.je.auth.util.AuthResult;
import com.je.auth.check.util.FoxUtil;

/**
 * OAuth2 请求处理类封装
 *
 * @author kong
 */
public interface OAuth2Handle {

    AuthEngine getAuthEngine();

    OAuth2Engine getOAuth2Engine();

    void setAuthEngine(AuthEngine authEngine);

    void setOAuth2Engine(OAuth2Engine oauth2Engine);

    /**
     * 处理Server端请求， 路由分发
     *
     * @return 处理结果
     */
    default AuthResult serverRequest() throws OAuth2Exception {
        // 获取变量
        AuthRequest req = getAuthEngine().getAuthCheckEngine().getAuthTokenContext().getRequest();
        AuthResponse res = getAuthEngine().getAuthCheckEngine().getAuthTokenContext().getResponse();
        OAuth2Config cfg = getOAuth2Engine().getoAuth2Config();

        // ------------------ 路由分发 ------------------

        // 模式一：Code授权码
        if (req.isPath(OAuth2Consts.Api.authorize) && req.isParam(OAuth2Consts.Param.response_type, OAuth2Consts.ResponseType.code)) {
            ClientModel cm = currClientModel();
            //如果配置允许授权码模式&&此客户端是授权码模式或自动模式
            if (cfg.getIsCode() && (cm.isCode || cm.isAutoMode)) {
                return authorize(req, res, cfg);
            }
            throw new OAuth2Exception("The Authorization mode is not opened!");
        }

        // Code授权码 获取 Access-Token
        if (req.isPath(OAuth2Consts.Api.token) && req.isParam(OAuth2Consts.Param.grant_type, OAuth2Consts.GrantType.authorization_code)) {
            return token(req, res, cfg);
        }

        // Refresh-Token 刷新 Access-Token
        if (req.isPath(OAuth2Consts.Api.refresh) && req.isParam(OAuth2Consts.Param.grant_type, OAuth2Consts.GrantType.refresh_token)) {
            return refreshToken(req);
        }

        // 回收 Access-Token
        if (req.isPath(OAuth2Consts.Api.revoke)) {
            return revokeToken(req);
        }

        // doLogin 登录接口
        if (req.isPath(OAuth2Consts.Api.doLogin)) {
            return doLogin(req, res, cfg);
        }

        // doConfirm 确认授权接口
        if (req.isPath(OAuth2Consts.Api.doConfirm)) {
            return doConfirm(req);
        }

        // 模式二：隐藏式
        if (req.isPath(OAuth2Consts.Api.authorize) && req.isParam(OAuth2Consts.Param.response_type, OAuth2Consts.ResponseType.token)) {
            ClientModel cm = currClientModel();
            if (cfg.getIsImplicit() && (cm.isImplicit || cm.isAutoMode)) {
                return authorize(req, res, cfg);
            }
            throw new OAuth2Exception("The Authorization mode is not opened!");
        }

        // 模式三：密码式
        if (req.isPath(OAuth2Consts.Api.token) && req.isParam(OAuth2Consts.Param.grant_type, OAuth2Consts.GrantType.password)) {
            ClientModel cm = currClientModel();
            if (cfg.getIsPassword() && (cm.isPassword || cm.isAutoMode)) {
                return password(req, res, cfg);
            }
            throw new OAuth2Exception("The Authorization mode is not opened!");
        }

        // 模式四：凭证式
        if (req.isPath(OAuth2Consts.Api.client_token) && req.isParam(OAuth2Consts.Param.grant_type, OAuth2Consts.GrantType.client_credentials)) {
            ClientModel cm = currClientModel();
            if (cfg.getIsClient() && (cm.isClient || cm.isAutoMode)) {
                return clientToken(req, res, cfg);
            }
            throw new OAuth2Exception("The Authorization mode is not opened!");
        }

        //加载资源
        if (req.isPath(OAuth2Consts.Api.doLoadResource)) {
            return doLoadResource(req, res, cfg);
        }

        // 默认返回
        return AuthResult.error(OAuth2Consts.NOT_HANDLE);
    }

    /**
     * 加载制定资源
     *
     * @param req
     * @param res
     * @param cfg
     * @return
     * @throws OAuth2Exception
     */
    default AuthResult doLoadResource(AuthRequest req, AuthResponse res, OAuth2Config cfg) throws OAuth2Exception {
        //如果没有登录，则要先去认证获取授权信息
        if (getAuthEngine().getAuthLoginManager().isLogin() == false) {
            return AuthResult.error("Please authorize to get the access token!");
        }

        //获取登录信息
        String loginId = (String) getAuthEngine().getAuthLoginManager().getLoginId();

        //accessToken
        String accessToken = req.getHeader(OAuth2Consts.Param.access_token);
        if (FoxUtil.isEmpty(accessToken)) {
            throw new OAuth2Exception("The access token must be specified!");
        }

        //客户端ID
        String clientId = req.getParam(OAuth2Consts.Param.client_id);
        if (FoxUtil.isEmpty(clientId)) {
            throw new OAuth2Exception("The client id must be specified!");
        }

        //获取资源类型
        String scope = req.getParamNotNull("scope");
        if (FoxUtil.isEmpty(scope)) {
            throw new OAuth2Exception("The scope must be specified!");
        }

        // 校验 Access-Token是否具有scope权限
        getOAuth2Engine().getAuth2Template().checkScope(accessToken, scope);

        if (!ResourceLoaderRegistry.containsKey(scope)) {
            throw new OAuth2Exception(String.format("Forbiddon load the scope %s！", scope));
        }

        //获取客户端
        ClientModel clientModel = getOAuth2Engine().getAuth2Template().getClientModel(clientId);
        return AuthResult.data(ResourceLoaderRegistry.get(scope).load(clientModel, loginId));
    }

    /**
     * 模式一：Code授权码 / 模式二：隐藏式
     *
     * @param req 请求对象
     * @param res 响应对象
     * @param cfg 配置对象
     * @return 处理结果
     */
    default AuthResult authorize(AuthRequest req, AuthResponse res, OAuth2Config cfg) throws OAuth2Exception {
        // 1、如果尚未登录, 则先去登录
        if (getAuthEngine().getAuthLoginManager().isLogin() == false) {
            String uri = getOAuth2Engine().getAuth2Template().buildLoginRedirectUri(req.getParamNotNull(OAuth2Consts.Param.client_id),
                    req.getParamNotNull(OAuth2Consts.Param.redirect_uri),
                    req.getParamNotNull(OAuth2Consts.Param.scope),
                    req.getParamNotNull(OAuth2Consts.Param.response_type),
                    req.getParamNotNull(OAuth2Consts.Param.state));
            return AuthResult.code(AuthResult.CODE_REDIRECT).setData(uri).setMsg("Please redirect to this url to login!");
        }

        // 2、构建请求Model
        RequestAuthModel ra = getOAuth2Engine().getAuth2Template().generateRequestAuth(req, getAuthEngine().getAuthLoginManager().getLoginId());

        // 3、校验：重定向域名是否合法
        getOAuth2Engine().getAuth2Template().checkRightUrl(ra.clientId, ra.redirectUri);

        // 4、校验：此次申请的Scope，该Client是否已经签约
        getOAuth2Engine().getAuth2Template().checkContract(ra.clientId, ra.scope);

        // 5、判断：如果此次申请的Scope，该用户尚未授权，则转到授权页面
        boolean isGrant = getOAuth2Engine().getAuth2Template().isGrant(ra.loginId, ra.clientId, ra.scope);
        if (isGrant == false) {
            String uri = getOAuth2Engine().getAuth2Template().buildConfirmRedirectUri(req.getParamNotNull(OAuth2Consts.Param.client_id),
                    req.getParamNotNull(OAuth2Consts.Param.redirect_uri),
                    req.getParamNotNull(OAuth2Consts.Param.scope),
                    req.getParamNotNull(OAuth2Consts.Param.response_type),
                    req.getParamNotNull(OAuth2Consts.Param.state));
            return AuthResult.code(AuthResult.CODE_REDIRECT).setData(uri);
        }

        // 6、判断授权类型
        // 如果是 授权码式，则：开始重定向授权，下放code
        if (OAuth2Consts.ResponseType.code.equals(ra.responseType)) {
            CodeModel codeModel = getOAuth2Engine().getAuth2Template().generateCode(ra);
            String redirectUri = getOAuth2Engine().getAuth2Template().buildRedirectUri(ra.redirectUri, codeModel.code, ra.state);
            return AuthResult.code(AuthResult.CODE_REDIRECT).setData(redirectUri);
        }

        // 如果是 隐藏式，则：开始重定向授权，下放 token
        if (OAuth2Consts.ResponseType.token.equals(ra.responseType)) {
            AccessTokenModel at = getOAuth2Engine().getAuth2Template().generateAccessToken(ra, false);
            String redirectUri = getOAuth2Engine().getAuth2Template().buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state);
            return AuthResult.code(AuthResult.CODE_REDIRECT).setData(redirectUri);
        }

        // 默认返回
        throw new OAuth2Exception("无效response_type: " + ra.responseType);
    }

    /**
     * Code授权码 获取 Access-Token
     *
     * @param req 请求对象
     * @param res 响应对象
     * @param cfg 配置对象
     * @return 处理结果
     */
    default AuthResult token(AuthRequest req, AuthResponse res, OAuth2Config cfg) throws OAuth2Exception {
        // 获取参数
        String code = req.getParamNotNull(OAuth2Consts.Param.code);
        String clientId = req.getParamNotNull(OAuth2Consts.Param.client_id);
        String clientSecret = req.getParamNotNull(OAuth2Consts.Param.client_secret);
        String redirectUri = req.getParam(OAuth2Consts.Param.redirect_uri);

        // 校验参数
        getOAuth2Engine().getAuth2Template().checkGainTokenParam(code, clientId, clientSecret, redirectUri);

        // 构建 Access-Token
        AccessTokenModel token = getOAuth2Engine().getAuth2Template().generateAccessToken(code);

        // 返回
        return AuthResult.data(token.toLineMap());
    }

    /**
     * Refresh-Token 刷新 Access-Token
     *
     * @param req 请求对象
     * @return 处理结果
     */
    default AuthResult refreshToken(AuthRequest req) throws OAuth2Exception {
        // 获取参数
        String clientId = req.getParamNotNull(OAuth2Consts.Param.client_id);
        String clientSecret = req.getParamNotNull(OAuth2Consts.Param.client_secret);
        String refreshToken = req.getParamNotNull(OAuth2Consts.Param.refresh_token);

        // 校验参数
        getOAuth2Engine().getAuth2Template().checkRefreshTokenParam(clientId, clientSecret, refreshToken);

        // 获取新Token返回
        Object data = getOAuth2Engine().getAuth2Template().refreshAccessToken(refreshToken).toLineMap();
        return AuthResult.data(data);
    }

    /**
     * 回收 Access-Token
     *
     * @param req 请求对象
     * @return 处理结果
     */
    default AuthResult revokeToken(AuthRequest req) throws OAuth2Exception {
        // 获取参数
        String clientId = req.getParamNotNull(OAuth2Consts.Param.client_id);
        String clientSecret = req.getParamNotNull(OAuth2Consts.Param.client_secret);
        String accessToken = req.getParamNotNull(OAuth2Consts.Param.access_token);

        // 如果 Access-Token 不存在，直接返回
        if (getOAuth2Engine().getAuth2Template().getAccessToken(accessToken) == null) {
            return AuthResult.ok("access_token不存在：" + accessToken);
        }

        // 校验参数
        getOAuth2Engine().getAuth2Template().checkAccessTokenParam(clientId, clientSecret, accessToken);

        // 获取新Token返回
        getOAuth2Engine().getAuth2Template().revokeAccessToken(accessToken);
        return AuthResult.ok();
    }

    /**
     * doLogin 登录接口
     *
     * @param req 请求对象
     * @param res 响应对象
     * @param cfg 配置对象
     * @return 处理结果
     */
    AuthResult doLogin(AuthRequest req, AuthResponse res, OAuth2Config cfg);

    /**
     * doConfirm 确认授权接口
     *
     * @param req 请求对象
     * @return 处理结果
     */
    default AuthResult doConfirm(AuthRequest req) throws OAuth2Exception {
        String clientId = req.getParamNotNull(OAuth2Consts.Param.client_id);
        String scope = req.getParamNotNull(OAuth2Consts.Param.scope);
        Object loginId = getAuthEngine().getAuthLoginManager().getLoginId();
        getOAuth2Engine().getAuth2Template().saveGrantScope(clientId, loginId, scope);
        return AuthResult.ok();
    }

    /**
     * 模式三：密码式
     *
     * @param req 请求对象
     * @param res 响应对象
     * @param cfg 配置对象
     * @return 处理结果
     */
    default AuthResult password(AuthRequest req, AuthResponse res, OAuth2Config cfg) throws OAuth2Exception {
        // 1、获取请求参数
        String clientId = req.getParamNotNull(OAuth2Consts.Param.client_id);
        String scope = req.getParam(OAuth2Consts.Param.scope, "");

        // 2、校验 ClientScope
        getOAuth2Engine().getAuth2Template().checkContract(clientId, scope);

        // 3、防止因前端误传token造成逻辑干扰
        getAuthEngine().getAuthCheckEngine().getAuthTokenContext().getStorage().set(getAuthEngine().getAuthLoginManager().splicingKeyJustCreatedSave(), "no-token");

        // 4、调用API 开始登录，如果没能成功登录，则直接退出
        AuthResult retObj = doLogin(req, res, cfg);
        if (getAuthEngine().getAuthLoginManager().isLogin() == false) {
            return retObj;
        }

        // 5、构建 ra对象
        RequestAuthModel ra = new RequestAuthModel();
        ra.clientId = clientId;
        ra.loginId = getAuthEngine().getAuthLoginManager().getLoginId();
        ra.scope = scope;

        // 7、生成 Access-Token
        AccessTokenModel at = getOAuth2Engine().getAuth2Template().generateAccessToken(ra, true);

        // 8、返回 Access-Token
        return AuthResult.data(at.toLineMap());
    }

    /**
     * 模式四：凭证式
     *
     * @param req 请求对象
     * @param res 响应对象
     * @param cfg 配置对象
     * @return 处理结果
     */
    default AuthResult clientToken(AuthRequest req, AuthResponse res, OAuth2Config cfg) throws OAuth2Exception {
        // 获取参数
        String clientId = req.getParamNotNull(OAuth2Consts.Param.client_id);
        String clientSecret = req.getParamNotNull(OAuth2Consts.Param.client_secret);
        String scope = req.getParam(OAuth2Consts.Param.scope);

        //校验 ClientScope
        getOAuth2Engine().getAuth2Template().checkContract(clientId, scope);

        // 校验 ClientSecret
        getOAuth2Engine().getAuth2Template().checkClientSecret(clientId, clientSecret);

        // 返回 Client-Token
        ClientTokenModel ct = getOAuth2Engine().getAuth2Template().generateClientToken(clientId, scope);

        // 返回 Client-Token
        return AuthResult.data(ct.toLineMap());
    }

    /**
     * 根据当前请求提交的 client_id 参数获取 SaClientModel 对象
     *
     * @return /
     */
    default ClientModel currClientModel() throws OAuth2Exception {
        String clientId = getAuthEngine().getAuthCheckEngine().getAuthTokenContext().getRequest().getParam(OAuth2Consts.Param.client_id);
        return getOAuth2Engine().getAuth2Template().checkClientModel(clientId);
    }

}
